package com.yunji.order.business.remote;

import com.alibaba.fastjson2.JSONObject;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.AutoUpdateCertificatesVerifier;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import com.yunji.common.utils.JsonUtil;
import com.yunji.common.utils.RandomUtils;
import com.yunji.common.utils.StringUtils;
import com.yunji.common.utils.bean.BeanUtils;
import com.yunji.order.bo.PayableBO;
import com.yunji.order.business.WechatBusiness;
import com.yunji.order.domain.RequestDO;
import com.yunji.order.emuns.PayMode;
import com.yunji.order.emuns.PayState;
import com.yunji.order.emuns.RemoteTypes;
import com.yunji.order.emuns.ResultTypes;
import com.yunji.order.service.RequestService;
import com.yunji.order.wechat.WechatRefund;
import com.yunji.order.wechat.WechatTrade;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;

import javax.annotation.Resource;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.*;
import java.util.Base64;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import static com.yunji.order.contstant.PayCode.*;
import static java.nio.charset.StandardCharsets.UTF_8;

/**
 * @Project: yunji-coffee
 * @Package: com.yunji.order.remote
 * @Title: WechatPayable
 * @Description: 微信支付业务处理
 * <p>
 * History:
 * Date                     Version     Author          Summary
 * ============================================================
 * 2024-04-11 18:31:28      V1.0        HuaAo       新建类
 */
@Component
public class WechatPayable implements PayableHandler {
    private static final Logger logs = LoggerFactory.getLogger(WechatPayable.class);

    private static final String HTTP_HEAD_ACCEPT = "Accept";
    private static final String HTTP_HEAD_CONTENT = "Content-type";

    private static final String HTTP_PARAM_JSON = "application/json";
    private static final String HTTP_PARAM_JSON_UTF8 = "application/json; charset=utf-8";

    // 交易商户标识参数名称
    private static final String WECHAT_HEAD_MCH_ID = "mchid";
    // 交易微信标识参数名称
    private static final String WECHAT_HEAD_APP_ID = "appid";
    // 交易响应地址参数名称
    private static final String WECHAT_HEAD_NOTIFY = "notify_url";
    // 交易流水号参数名称
    private static final String WECHAT_HEAD_TRADE = "out_trade_no";
    // 交易详情描述参数名称
    private static final String WECHAT_HEAD_DESCRIPTION = "description";

    // 交易金额参数名称
    private static final String WECHAT_REQUEST_TOTAL = "total";
    // 退款金额参数名称
    private static final String WECHAT_REQUEST_REFUND = "refund";
    // 交易金额参数名称
    private static final String WECHAT_REQUEST_AMOUNT = "amount";

    @Value("${wechat.member.appid:wxee778910359d3e4b}")
    private String memberAppid;

    @Value("${wechat.callback.payable:xxx}")
    private String noticeURLPayable;
    @Value("${wechat.callback.recharge:xxx}")
    private String noticeURLRecharge;
    @Value("${wechat.callback.refund:xxx}")
    private String noticeURLRefund;
    @Value("${wechat.callback.payout:xxx}")
    private String noticeURLPayout;


    @Resource
    private RequestService requestService;

    /**------------------------------------- 响应验签 -------------------------------------**/

    /**
     * @return AutoUpdateCertificatesVerifier 自动更新证书验证程序
     * @Title getVerifier
     * @Desc 获取自动更新证书验证程序
     * @Date 2024-04-11 20:09:56
     */
    private AutoUpdateCertificatesVerifier getVerifier() {
        // 加载商家私钥
        PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new ByteArrayInputStream(WECHAT_PRIVATE_KEY.getBytes(UTF_8)));
        // 初始化商家私钥签名器
        PrivateKeySigner keySigner = new PrivateKeySigner(MCH_SERIAL_NO, merchantPrivateKey);
        // 初始化微信凭证
        WechatPay2Credentials credentials = new WechatPay2Credentials(MCH_ID, keySigner);
        // 使用自动更新的签名验证器,不需要传入证书
        return new AutoUpdateCertificatesVerifier(credentials, API_V3KEY.getBytes(UTF_8));
    }

    /**
     * @return boolean true 验证成功 false 验证失败
     * @Title signVerify
     * @Desc 验证签名
     * @Date 2024-04-11 19:39:33
     * @Param serial 验证接口
     * @Param message 数据信息
     * @Param signature 支付签名
     */
    public boolean signVerify(String serial, String message, String signature) {
        AutoUpdateCertificatesVerifier verifier = getVerifier();
        // 验证信息
        return verifier.verify(serial, message.getBytes(UTF_8), signature);
    }

    /**
     * @return String JSON信息
     * @Title decryptTrade
     * @Desc 解密交易信息
     * @Date 2024-04-14 14:47:18
     * @Param context 交易内容信息
     */
    public String decryptTrade(String context) {
        try {
            AesUtil util = new AesUtil(API_V3KEY.getBytes(UTF_8));
            ObjectMapper mapper = new ObjectMapper();
            JsonNode node = mapper.readTree(context);
            JsonNode resource = node.get("resource");
            String ciphertext = resource.get("ciphertext").textValue();
            String associatedData = resource.get("associated_data").textValue();
            String nonce = resource.get("nonce").textValue();
            return util.decryptToString(associatedData.getBytes(UTF_8), nonce.getBytes(UTF_8), ciphertext);
        } catch (Exception e) {
            logs.error("【微信支付】解密失败", e);
        }
        return null;
    }

    /**------------------------------------- 公共请求 -------------------------------------**/

    /**
     * @return CloseableHttpClient HTTP请求客户端
     * @Title buildHTTPClient
     * @Desc 构建微信请求客户端
     * @Date 2024-04-12 10:07:34
     */
    private CloseableHttpClient buildHTTPClient() {
        // 加载商家私钥
        PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new ByteArrayInputStream(WECHAT_PRIVATE_KEY.getBytes(UTF_8)));
        // 初始化商家私钥签名器
        PrivateKeySigner keySigner = new PrivateKeySigner(MCH_SERIAL_NO, merchantPrivateKey);
        // 初始化微信凭证
        WechatPay2Credentials credentials = new WechatPay2Credentials(MCH_ID, keySigner);
        // 使用自动更新的签名验证器，不需要传入证书
        AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(credentials, API_V3KEY.getBytes(UTF_8));

        WechatPay2Validator validator = new WechatPay2Validator(verifier);
        // 构建请求客户端
        return WechatPayHttpClientBuilder.create().withMerchant(MCH_ID, MCH_SERIAL_NO, merchantPrivateKey).withValidator(validator).build();
    }

    /**
     * @param url    请求地址
     * @param mapper 构建mapper
     * @param node   参数对象
     * @return CloseableHttpResponse 响应信息
     * @Title sendRequest
     * @Desc 发送微信交易请求
     * @Date 2024-04-22 17:03:38.641
     */
    private CloseableHttpResponse sendRequest(String url, ObjectMapper mapper, ObjectNode node) throws IOException {
        logs.info("【微信交易】请求地址:{},请求参数:{}", url, node);
        CloseableHttpClient client = buildHTTPClient();

        HttpPost post = new HttpPost(url);
        post.addHeader(HTTP_HEAD_ACCEPT, HTTP_PARAM_JSON);
        post.addHeader(HTTP_HEAD_CONTENT, HTTP_PARAM_JSON_UTF8);

        try {
            ByteArrayOutputStream stream = new ByteArrayOutputStream();
            mapper.writeValue(stream, node);
            post.setEntity(new StringEntity(stream.toString(ENCODING_UTF8), ENCODING_UTF8));
        } catch (IOException e) {
            logs.error("【微信交易】序列化参数异常,请求地址:{},请求参数:{}", url, node, e);
        }

        return client.execute(post);
    }

    /**
     * @return String
     * @Title buildSign
     * @Desc 构建签名信息
     * @Date 2024-04-14 14:36:47
     * @Param timestamp 当前时间戳
     * @Param nonce 随机数
     * @Param packageStr 预支付交易会话ID
     */
    private String buildSign(String timestamp, String nonce, String packageStr) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
        //应用id
        String build = memberAppid + "\n" +
                // 时间戳
                timestamp + "\n" +
                // 随机字符串
                nonce + "\n" +
                // 预支付交易会话ID
                packageStr + "\n";

        byte[] message = build.getBytes(UTF_8);
        PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new ByteArrayInputStream(WECHAT_PRIVATE_KEY.getBytes(UTF_8)));
        Signature sign = Signature.getInstance("SHA256withRSA");
        sign.initSign(merchantPrivateKey);
        sign.update(message);
        return Base64.getEncoder().encodeToString(sign.sign());
    }

    /**------------------------------------- 购买交易 -------------------------------------**/


    /**
     * @return WechatOrder 微信支付订单信息
     * @Title createTrade
     * @Desc 创建微信支付交易信息
     * @Date 2024-04-12 10:11:06
     * @Param tradeCode 交易流水号
     * @Param desc 支付秒速
     * @Param amount 支付金额
     * @Param openId 微信OPENID
     */
    public WechatTrade createTrade(PayableBO param)
            throws IOException, NoSuchAlgorithmException, SignatureException, InvalidKeyException {
        logs.info("【微信支付】交易信息:【{}】", param);

        RequestDO request = new RequestDO();
        request.setChannel(PayMode.WECHAT.getCode());
        request.setRequestType(RemoteTypes.WECHAT_JSAPI_PAYABLE.getCode());
        request.setRequestUrl(WECHAT_TRADE_PAYABLE);
        request.setTradeCode(param.getTradeCode());

        ObjectMapper mapper = new ObjectMapper();
        ObjectNode node = mapper.createObjectNode();
        node.put(WECHAT_HEAD_MCH_ID, MCH_ID)                        // 直连商户号
                .put(WECHAT_HEAD_APP_ID, memberAppid)                    // 公众号ID
                .put(WECHAT_HEAD_NOTIFY, noticeURLPayable)            // 通知地址 异步接收微信支付结果通知的回调地址
                .put(WECHAT_HEAD_DESCRIPTION, param.getTradeContent())    // 商品描述
                .put(WECHAT_HEAD_TRADE, param.getTradeCode());            // 商户订单号 商户系统内部订单号 只能是数字、大小写字母_-*且在同一个商户号下唯一

        node.putObject(WECHAT_REQUEST_AMOUNT)                        // 订单金额 订单金额信息
                .put(WECHAT_REQUEST_TOTAL, param.getAmount());            // 总金额 订单总金额，单位为分

        node.putObject("payer")                            // 支付者信息
                .put("openid", param.getOpenId());            // 用户标识 用户在普通商户AppID下的唯一标识

        request.setRequestParam(String.valueOf(node));
        // 发送交易请求
        try (CloseableHttpResponse response = sendRequest(WECHAT_TRADE_PAYABLE, mapper, node)) {
            String result = EntityUtils.toString(response.getEntity());
            request.setResponseContent(result);
            request.setStatus(ResultTypes.SUCCESS.getCode());

            logs.info("【微信支付】支付响应:{}", result);
            param.setResult(result);

            JsonNode json = mapper.readTree(result);

            JsonNode prepayId = json.get("prepay_id");
            Assert.isTrue(BeanUtils.isNotNull(prepayId), "创建预支付订单失败");

            String packageStr = "prepay_id=" + prepayId.textValue();
            logs.info("【微信支付】预支付交易会话ID:{}", packageStr);
            String timestamp = System.currentTimeMillis() + "";
            String nonce = RandomUtils.randomString(32);

            String ciphertext = buildSign(timestamp, nonce, packageStr);
            logs.info("【微信支付】支付签名信息:{}", ciphertext);

            WechatTrade wechat = new WechatTrade();
            wechat.setNonceStr(nonce);
            wechat.setPackageStr(packageStr);
            wechat.setTimestamp(timestamp);
            wechat.setSign(ciphertext);
            wechat.setPrepay_id(prepayId.textValue());
            wechat.setAppId(memberAppid);
            wechat.setMchId(MCH_ID);
            return wechat;
        } catch (IOException e) {
            logs.error("【微信支付】构建支付信息失败", e);
        } finally {
            requestService.createRequest(request);
        }
        return null;
    }

    /**------------------------------------- 退款交易 -------------------------------------**/

    /**
     * @return WechatRefund 微信退款信息
     * @Title tradeRefund
     * @Desc 交易退款
     * @Date 2024-04-14 14:50:44
     * param    微信退款请求参数
     */
    public WechatRefund tradeRefund(PayableBO param) {
        logs.info("【微信退款】参数信息:{}", param);
        String refundCode = param.getRefundCode();
        RequestDO request = new RequestDO();
        request.setChannel(PayMode.WECHAT.getCode());
        request.setRequestType(RemoteTypes.WECHAT_TRADE_REFUND.getCode());
        request.setRequestUrl(WECHAT_TRADE_REFUND);
        request.setTradeCode(refundCode);

        ObjectMapper mapper = new ObjectMapper();
        ObjectNode node = mapper.createObjectNode();
        node.put("out_refund_no", refundCode)            // 商户退款单号  商户系统内部的退款单号，商户系统内部唯一，只能是数字、大小写字母_-|*@
//          .put("transaction_id", param.getTradeId())			// 微信支付订单号  原支付交易对应的微信订单号，与out_trade_no二选一
                .put(WECHAT_HEAD_NOTIFY, noticeURLRefund)        // 退款结果回调url  异步接收微信支付退款结果通知的回调地址
                .put(WECHAT_HEAD_TRADE, param.getTradeCode());        // 商户订单号  原支付交易对应的商户订单号 与transaction_id二选一
        node.putObject(WECHAT_REQUEST_AMOUNT)                    // 订单金额信息
                .put(WECHAT_REQUEST_TOTAL, param.getAmount())        // 原支付交易的订单总金额，单位为分，只能为整数
                .put(WECHAT_REQUEST_REFUND, param.getRefund())        // 退款金额，单位为分，只能为整数，不能超过原订单支付金额
                .put("currency", "CNY");                // 符合ISO 4217标准的三位字母代码，目前只支持人民币：CNY
        logs.info("【微信退款】退款信息{}", node);
        request.setRequestParam(String.valueOf(node));
        try (CloseableHttpResponse response = sendRequest(WECHAT_TRADE_REFUND, mapper, node)) {
            String result = EntityUtils.toString(response.getEntity());
            request.setResponseContent(result);
            request.setStatus(ResultTypes.SUCCESS.getCode());
            logs.info("【微信退款】退款响应:{}", result);
            param.setResult(result);
            JsonNode json = mapper.readTree(result);
            // 将微信报文转换成本平台订单，方便入库
            WechatRefund trade = new WechatRefund();
            JsonNode status = json.get("status");
            if (status == null) {
                logs.error(" wechatPayable tradeRefund param:{} error callback msg:{}", JsonUtil.toJsonString(param), json.get("message"));
                WechatRefund wechatRefund = new WechatRefund();
                wechatRefund.setStatus(WechatBusiness.FAIL);
                return wechatRefund;
            }
            trade.setStatus(status.textValue());
            trade.setSuccess(WechatBusiness.SUCCESS.equals(trade.getStatus()) || WechatBusiness.PROCESSING.equals(trade.getStatus()));
            if (trade.isSuccess()) {
                trade.setRefundId(json.get("refund_id").textValue());
                trade.setTradeId(json.get("transaction_id").textValue());
                trade.setRefundCode(json.get("out_refund_no").textValue());
                trade.setTradeCode(json.get(WECHAT_HEAD_TRADE).textValue());
                trade.setChannel(json.get("channel").textValue());
                trade.setUserAccount(json.get("user_received_account").textValue());
                JsonNode amount = json.get(WECHAT_REQUEST_AMOUNT);
                trade.setDiscountRefund(amount.get("discount_refund").longValue());
                trade.setTotal(amount.get(WECHAT_REQUEST_TOTAL).longValue());
                trade.setRefund(amount.get(WECHAT_REQUEST_REFUND).longValue());
                trade.setPayerTotal(amount.get("payer_total").longValue());
                trade.setPayerRefund(amount.get("payer_refund").longValue());
                trade.setSettleTotal(amount.get("settlement_total").longValue());
                trade.setSettleRefund(amount.get("settlement_refund").longValue());
            }
            logs.info("response:【{}】", trade);
            return trade;
        } catch (Exception e) {
            logs.error("【微信退款】交易{}退款处理异常", refundCode, e);
            WechatRefund wechatRefund = new WechatRefund();
            wechatRefund.setStatus(WechatBusiness.FAIL);
            return wechatRefund;
        } finally {
            requestService.createRequest(request);
        }
    }

    /**------------------------------------- 关闭交易 -------------------------------------**/

    /**
     * @Title closeTrade
     * @Desc 关闭微信交易信息
     * @Date 2024-04-12 10:02:33
     * @Param outTradeNo 订单流水号
     */
//    public void closeTrade(String outTradeNo) throws IOException {
//        logs.info("【关闭交易】交易流水号{}", outTradeNo);
//        String url = StringUtils.format(WECHAT_TRADE_CLOSED, outTradeNo);
//
//        ObjectMapper mapper = new ObjectMapper();
//        ObjectNode node = mapper.createObjectNode();
//        node.put(WECHAT_HEAD_MCH_ID, MCH_ID);
//        logs.info("【关闭交易】关闭信息:{}", node);
//        CloseableHttpResponse response = sendRequest(url, mapper, node);
//
//        logs.info("【关闭交易】状态码:{}", response.getStatusLine().getStatusCode());
//    }

    /**------------------------------------- 转账交易 -------------------------------------**/

    /**
     * @param param 微信参数信息
     * @return WechatPayout
     * @Title payoutTrade
     * @Desc 转账交易
     * @Date 2024-04-22 17:28:37.195
     */
    @Override
    public Map<String, String> payoutTrade(PayableBO param) {
        logs.info("【微信转账】参数信息:{}", param);

        RequestDO request = new RequestDO();
        request.setChannel(PayMode.WECHAT.getCode());
        request.setRequestType(RemoteTypes.WECHAT_TRADE_PAYOUT.getCode());
        request.setRequestUrl(WECHAT_TRADE_PAYOUT);
        request.setTradeCode(param.getTradeCode());

        ObjectMapper mapper = new ObjectMapper();
        ObjectNode node = mapper.createObjectNode();
        node.put(WECHAT_HEAD_APP_ID, memberAppid)                    // 商户appid
                .put(WECHAT_HEAD_NOTIFY, noticeURLPayout)            // 通知地址 异步接收微信支付结果通知的回调地址
                .put("out_batch_no", param.getTradeCode())        // 商家批次单号 商户系统内部的商家批次单号
                .put("batch_name", param.getTradeCode())        // 批次名称 该笔批量转账的名称
                .put("batch_remark", param.getTradeCode())        // 批次名称 该笔批量转账的名称
                .put("total_amount", param.getAmount())        // 转账总金额 转账金额单位为"分"
                .put("total_num", 1);


        ArrayNode array = node.putArray("transfer_detail_list");    // 转账明细列表 最多一千笔
        ObjectNode detail = array.addObject();
        detail.put("out_detail_no", param.getTradeCode())            // 商家明细单号
                .put("transfer_amount", param.getAmount())            // 转账金额 转账金额单位为“分”
                .put("transfer_remark", param.getTradeContent())        // 转账备注 单条转账备注(微信用户会收到该备注) 最多允许32个字符
                .put("openid", param.getOpenId());                    // 收款用户openid

        request.setRequestParam(String.valueOf(node));
        logs.info("【微信转账】转账信息{}", node);

        try (CloseableHttpResponse response = sendRequest(WECHAT_TRADE_PAYOUT, mapper, node)) {
            String result = EntityUtils.toString(response.getEntity());
            request.setResponseContent(result);
            request.setStatus(ResultTypes.SUCCESS.getCode());

            logs.info("【微信转账】转账响应:{}", result);
            param.setResult(result);

            JsonNode json = mapper.readTree(result);

            Map<String, String> payout = new HashMap<>();
            payout.put("tradeCode", json.get("out_batch_no").textValue());
            payout.put("payCode", json.get("batch_id").textValue());
            payout.put("createTime", json.get("create_time").textValue());
            return payout;
        } catch (Exception e) {
            logs.error("【微信转账】转账处理失败", e);
        } finally {
            requestService.createRequest(request);
        }
        return Collections.emptyMap();
    }

    /**
     * ------------------------------------- 查询购买 -------------------------------------
     **/

    public void searchTrade(String tradeCode) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException, IOException {
        String timestamp = System.currentTimeMillis() + "";
        String nonce = RandomUtils.randomString(32);
        String packageStr = "out_trade_no=" + tradeCode;
        String ciphertext = buildSign(timestamp, nonce, packageStr);
        logs.info("【微信查询交易】查询签名信息:{}", ciphertext);
        String url = StringUtils.format(WECHAT_SEARCH_PAYABLE, tradeCode, MCH_ID);
        CloseableHttpClient client = buildHTTPClient();

        HttpGet http = new HttpGet(url);
        http.setHeader(HTTP_HEAD_ACCEPT, HTTP_PARAM_JSON);

        try (CloseableHttpResponse response = client.execute(http)) {
            String result = EntityUtils.toString(response.getEntity());
            logs.info("【微信查询交易】响应结果：{}", result);
        } catch (Exception e) {
            logs.error("【微信查询交易】查询失败", e);
        }
    }

    /**
     * ------------------------------------- 查询退款 -------------------------------------
     **/

    public void searchRefund(String tradeCode) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException {
        String timestamp = System.currentTimeMillis() + "";
        String nonce = RandomUtils.randomString(32);
        String packageStr = "out_refund_no=" + tradeCode;
        String ciphertext = buildSign(timestamp, nonce, packageStr);
        logs.info("【微信查询退款】查询签名信息:{}", ciphertext);
        String url = StringUtils.format(WECHAT_SEARCH_REFUND, tradeCode);
        CloseableHttpClient client = buildHTTPClient();
        HttpGet http = new HttpGet(url);
        http.setHeader(HTTP_HEAD_ACCEPT, HTTP_PARAM_JSON);

        try (CloseableHttpResponse response = client.execute(http)) {
            String result = EntityUtils.toString(response.getEntity());
            logs.info("【微信查询退款】响应结果：{}", result);
            JSONObject json = JSONObject.parse(result);
            logs.info("【微信查询退款】查询结果-{}", json);
            Assert.isTrue(BeanUtils.isNotNull(json), "解析失败");
            String wechatState = json.getString("refund_status");
            String wechatContent = PayState.get(wechatState);
            logs.info("【微信查询退款】回调状态{}:{}", wechatState, wechatContent);
            Assert.isTrue(PayState.SUCCESS.getCode().equals(wechatState), "微信退款回调结果失败");
        } catch (Exception e) {
            logs.error("【微信查询退款】查询失败", e);
        }
    }

    /**
     * ------------------------------------- 查询转账 -------------------------------------
     **/

    public void searchPayout(String tradeCode) {

    }
}

